package org.budo.sharding.proxy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.shardingsphere.core.database.DatabaseTypes;
import org.apache.shardingsphere.core.execute.sql.execute.SQLExecuteCallback;
import org.apache.shardingsphere.core.rule.Authentication;
import org.apache.shardingsphere.core.rule.ProxyUser;
import org.apache.shardingsphere.shardingproxy.backend.communication.jdbc.recognizer.spi.JDBCDriverURLRecognizer;
import org.apache.shardingsphere.shardingproxy.backend.schema.LogicSchemas;
import org.apache.shardingsphere.shardingproxy.config.yaml.YamlDataSourceParameter;
import org.apache.shardingsphere.shardingproxy.context.ShardingProxyContext;
import org.apache.shardingsphere.shardingproxy.frontend.bootstrap.ShardingProxy;
import org.apache.shardingsphere.spi.database.DataSourceMetaData;
import org.apache.shardingsphere.spi.database.DatabaseType;
import org.budo.jdbc.driver.JdbcUrl;
import org.budo.sharding.proxy.recognizer.BudoShardingJdbcUrlRecognizer;
import org.budo.sharding.proxy.schema.BudoShardingLogicSchema;
import org.budo.sharding.spi.database.BudoShardingDataSourceMetaData;
import org.budo.support.lang.util.ReflectUtil;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * @author lmw
 */
@Getter
@Setter
@Accessors(fluent = true)
public class BudoShardingProxy {
    // proxy 配置
    private JdbcUrl proxy = new JdbcUrl(3388, "root", "");

    // 目标地址信息
    private Map<String, JdbcUrl> targetMap = new HashMap<String, JdbcUrl>();

    public void start() {
        // DBMeta信息
        this.prepareDataSourceMetaData();

        // 连接地址识别
        this.prepareJdbcUrlRecognizer();

        // 账号密码信息
        this.initAuthentication();

        // 逻辑库信息
        this.initLogicSchema();

        // 启动 Proxy
        ShardingProxy shardingProxy = ShardingProxy.getInstance();
        shardingProxy.start(this.proxy().getPort());
    }

    /**
     * 绕过本来的加载路径,直接反射加入到缓存中,供后续使用
     */
    private void prepareJdbcUrlRecognizer() {
        LinkedList<JDBCDriverURLRecognizer> jdbcDriverURLRecognizers = BudoShardingJdbcUrlRecognizer.jdbcDriverURLRecognizers();

        for (Entry<String, JdbcUrl> target : this.targetMap().entrySet()) {
            JdbcUrl jdbcUrl = target.getValue();
            String prefix = JdbcUrl.findPrefix(jdbcUrl.getUrl());
            BudoShardingJdbcUrlRecognizer jdbcUrlRecognizer = new BudoShardingJdbcUrlRecognizer(new String[] { prefix }, jdbcUrl.getDriver());
            jdbcDriverURLRecognizers.addFirst(jdbcUrlRecognizer); // 插入到第一个
        }
    }

    /**
     * 绕过本来的加载路径,直接反射加入到缓存中,供后续使用
     */
    private void prepareDataSourceMetaData() {
        Object METADATA = ReflectUtil.getFieldValue(SQLExecuteCallback.class, "CACHED_DATASOURCE_METADATA");
        Map<String, DataSourceMetaData> cachedDatasourceMetadata = (Map) METADATA;

        for (Entry<String, JdbcUrl> target : this.targetMap().entrySet()) {
            JdbcUrl jdbcUrl = target.getValue();
            String prefix = JdbcUrl.findPrefix(jdbcUrl.getUrl());
            BudoShardingDataSourceMetaData dataSourceMetaData = new BudoShardingDataSourceMetaData(jdbcUrl.getUrl(), new String[] { prefix });
            cachedDatasourceMetadata.put(target.getValue().getUrl(), dataSourceMetaData);
        }
    }

    private void initAuthentication() {
        String username = this.proxy().getUsername();
        String password = this.proxy().getPassword();

        ShardingProxyContext shardingProxyContext = ShardingProxyContext.getInstance();

        Authentication authentication = new Authentication();
        ArrayList<String> authorizedSchemas = new ArrayList<String>();
        authentication.getUsers().put(username, new ProxyUser(password, authorizedSchemas));

        Properties props = new Properties();
        shardingProxyContext.init(authentication, props);
    }

    private void initLogicSchema() {
        LogicSchemas logicSchemas = LogicSchemas.getInstance();

        // set databaseType MySQL
        DatabaseType databaseType = DatabaseTypes.getActualDatabaseType("MySQL"); // MySQL
        ReflectUtil.setFieldValue(logicSchemas, "databaseType", databaseType);

        for (Entry<String, JdbcUrl> entry : this.targetMap().entrySet()) {
            String schema = entry.getKey();

            Map<String, YamlDataSourceParameter> dataSources = this.dataSourceMap(entry.getValue());
            BudoShardingLogicSchema logicSchema = new BudoShardingLogicSchema(schema, dataSources);

            logicSchemas.getLogicSchemas().put(schema, logicSchema);
        }
    }

    private Map<String, YamlDataSourceParameter> dataSourceMap(JdbcUrl target) {
        String username = target.getUsername();
        if (null == username || username.trim().isEmpty()) {
            throw new IllegalArgumentException("#128 username is null, target=" + target);
        }

        String password = target.getPassword();
        String url = target.getUrl();
        String catalog = target.getCatalog();

        YamlDataSourceParameter dataSourceParameter = new YamlDataSourceParameter();
        dataSourceParameter.setUrl(url);
        dataSourceParameter.setUsername(username); // 目标数据库的连接
        dataSourceParameter.setPassword(password);

        Map<String, YamlDataSourceParameter> dataSources = new HashMap<String, YamlDataSourceParameter>();
        dataSources.put(catalog, dataSourceParameter);
        return dataSources;
    }
}